package com.crois.barrier.web.loadbalance.rule;

import cn.hutool.core.collection.CollectionUtil;
import com.crois.barrier.common.dto.ApiRouteInfo;
import com.crois.barrier.common.dto.BarrierClientInstance;
import com.crois.barrier.web.support.ServerWebExchangeUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.server.ServerWebExchange;

import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Hou Ze Yu
 * @description  权重轮询 负载均衡 规则
 * @date 2020/7/17
 */
public class WeightRoundRobinLoadBalanceRule implements LoadBalanceRule {


    private static Logger LOGGER = LoggerFactory.getLogger(WeightRoundRobinLoadBalanceRule.class);

    private ConcurrentHashMap<String, ConcurrentHashMap<String, WeightRoundRobin>> weightRoundRobinMap =  new ConcurrentHashMap<>();

    @Override
    public BarrierClientInstance choose(List<BarrierClientInstance> barrierClientInstanceList, ServerWebExchange serverWebExchange) {

        LOGGER.info("[Begin WeightRoundRobinLoadBalanceRule Choose]");

        String requestPath = String.valueOf(serverWebExchange.getAttributes().get(ServerWebExchangeUtils.BARRIER_REQUEST_PATH));

        ApiRouteInfo checkedApiRouteInfo  = (ApiRouteInfo) serverWebExchange.getAttributes().get(ServerWebExchangeUtils.BARRIER_ROUTE_ATTR);

        if (CollectionUtils.isEmpty(barrierClientInstanceList)|| StringUtils.isEmpty(requestPath)) {
            return null;
        }
        ConcurrentHashMap<String, WeightRoundRobin> apiWeightRoundRobinMap = weightRoundRobinMap.get(requestPath);
        WeightRoundRobin weightRoundRobin;
        if (CollectionUtil.isEmpty(apiWeightRoundRobinMap)){
            apiWeightRoundRobinMap =new ConcurrentHashMap<>();
            weightRoundRobin = new WeightRoundRobin();
            apiWeightRoundRobinMap.putIfAbsent(checkedApiRouteInfo.getApiRouteName(),weightRoundRobin);
            weightRoundRobinMap.putIfAbsent(requestPath,apiWeightRoundRobinMap);
        }else {
            weightRoundRobin = apiWeightRoundRobinMap.get(checkedApiRouteInfo.getApiRouteName());
            if (weightRoundRobin==null){
                weightRoundRobin = new WeightRoundRobin();
                apiWeightRoundRobinMap.putIfAbsent(checkedApiRouteInfo.getApiRouteName(),weightRoundRobin);
            }
        }
        BarrierClientInstance checkedClientInstance = weightRoundRobin.choose(barrierClientInstanceList);

        if (checkedClientInstance == null){
            return null;
        }else {
            LOGGER.info("[WeightRoundRobinLoadBalance choose success],serviceName:{}, instanceIp:{},instancePort:{},weight:{}"
                    ,checkedClientInstance.getServiceName()
                    ,checkedClientInstance.getIp()
                    ,checkedClientInstance.getPort()
                    ,checkedClientInstance.getWeight());
            return checkedClientInstance;
        }

    }

    static  class WeightRoundRobin{
       /**上次选择的服务器*/
       private int currentIndex = -1;
       /**当前调度的权值*/
       private int currentWeight = 0;
       /**最大权重*/
       private int maxWeight;
       /**权重的最大公约数*/
       private int gcdWeight;
       /**服务器数*/
       private int serverCount;


       /*
        * 得到两值的最大公约数
        */
       public int greaterCommonDivisor(int a, int b){
           if(a % b == 0){
               return b;
           }else{
               return greaterCommonDivisor(b,a % b);
           }
       }

       /*
        * 得到list中所有权重的最大公约数，实际上是两两取最大公约数d，然后得到的d
        * 与下一个权重取最大公约数，直至遍历完
        */
       public int greatestCommonDivisor(List<BarrierClientInstance> servers){
           int divisor = 0;
           for(int index = 0, len = servers.size(); index < len - 1; index++){
               if(index ==0){
                   divisor = greaterCommonDivisor(
                           (int) servers.get(index).getWeight(),(int)  servers.get(index + 1).getWeight());
               }else{
                   divisor = greaterCommonDivisor(divisor, (int) servers.get(index).getWeight());
               }
           }
           return divisor;
       }

       /*
        * 得到list中的最大的权重
        */
       public int greatestWeight(List<BarrierClientInstance> servers){
           int weight = 0;
           for(BarrierClientInstance server : servers){
               if(weight < server.getWeight()){
                   weight = (int) server.getWeight();
               }
           }
           return weight;
       }

       public BarrierClientInstance choose(List<BarrierClientInstance> doorClientInstanceList) {
           init(doorClientInstanceList);
           LOGGER.info("[Begin WeightRoundRobinLoadBalanceRule Choose]");
           while(true){
               currentIndex = (currentIndex + 1) % serverCount;
               if(currentIndex == 0){
                   currentWeight = currentWeight - gcdWeight;
                   if(currentWeight <= 0){
                       currentWeight = maxWeight;
                       if(currentWeight == 0){
                           return null;
                       }
                   }
               }
               if(doorClientInstanceList.get(currentIndex).getWeight() >= currentWeight){
                   LOGGER.info("[WeightRoundRobinLoadBalanceRule choose success]" +
                                   ",serviceName:{}" +
                                   ", instanceIp:{}" +
                                   ",instancePort:{}" +
                                   ",instanceWeight:{}"
                           ,doorClientInstanceList.get(currentIndex).getServiceName()
                           ,doorClientInstanceList.get(currentIndex).getIp()
                           ,doorClientInstanceList.get(currentIndex).getPort()
                           ,doorClientInstanceList.get(currentIndex).getWeight());
                   return doorClientInstanceList.get(currentIndex);
               }
           }
       }

       private void init(List<BarrierClientInstance> doorClientInstanceList) {
           maxWeight = greatestWeight(doorClientInstanceList);
           gcdWeight = greatestCommonDivisor(doorClientInstanceList);
           serverCount = doorClientInstanceList.size();
       }
    }

}
